package com.prb.framework.factory;

import com.prb.framework.annotation.*;
import com.prb.framework.bean.BeanDefinition;
import com.prb.framework.invoke.HandlerMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class AnnotationBeanFactory {

    private static Map<String , Object> beanMap = new ConcurrentHashMap<>();

    private static Map<String , BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    public static List<HandlerMethod> handlerMethodList = new ArrayList<>();

    public static void doScan(String pagePath) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {

        File file = getFile(pagePath);

        //构建 BeanDefinition
        buildBeanDefinition(file);

        //创建bean
        doCreateBean();

        //进行依赖注入
        injectBean();

        //构建 请求器映射器
        buildRequestMappingHandlerMapping();

        buildJurAop();

        fullHandlerMappingTarget();

    }


    private static void fullHandlerMappingTarget() {

        for (HandlerMethod handlerMethod : handlerMethodList) {
            handlerMethod.setTarget(beanMap.get(handlerMethod.getBeanId()));
        }

    }

    private static void buildJurAop() {

        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {

            Object value = entry.getValue();
            Object proxy;

            if(value.getClass().getAnnotation(PrbSecurity.class) != null){
                Class<?>[] interfaces = value.getClass().getInterfaces();
                if(interfaces.length == 1){
                    proxy = JurProxyFactory.getCglibProxy(value);
                }else{
                    proxy = JurProxyFactory.getCglibProxy(value);
                }
                beanMap.put(getBeanId(value.getClass()) , proxy);
                continue;
            }else{
                Method[] methods = value.getClass().getDeclaredMethods();
                for (Method method : methods) {
                    if(method.getAnnotation(PrbSecurity.class) != null){
                        Class<?>[] interfaces = value.getClass().getInterfaces();
                        if(interfaces.length == 1){
                            proxy = JurProxyFactory.getCglibProxy(value);
                        }else{
                            proxy = JurProxyFactory.getCglibProxy(value);
                        }
                        beanMap.put(getBeanId(value.getClass()) , proxy);
                        break;
                    }
                }
            }

        }

    }

    private static void buildRequestMappingHandlerMapping() {

        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {

            Object value = entry.getValue();
            PrbController annotation = value.getClass().getAnnotation(PrbController.class);
            if(annotation != null){
                String rootPath = value.getClass().getAnnotation(PrbRequestMapping.class) != null ? value.getClass().getAnnotation(PrbRequestMapping.class).value() : "";
                Method[] methods = value.getClass().getDeclaredMethods();

                for (Method method : methods) {
                    if(method.getAnnotation(PrbRequestMapping.class) != null){

                        HandlerMethod hm = new HandlerMethod();
                        String reqPath = method.getAnnotation(PrbRequestMapping.class) != null ? method.getAnnotation(PrbRequestMapping.class).value() : "";
                        hm.setBeanId(getBeanId(value.getClass()));
                        hm.setReqPath(rootPath + reqPath);
                        hm.setMethod(method);
                        hm.setTarget(value);
                        hm.setParam(buildParamMap(method));
                        handlerMethodList.add(hm);

                    }
                }

            }

        }
    }

    private static Map<String, Integer> buildParamMap(Method method) {

        Map<String , Integer> paramMap = new HashMap<>();
        Parameter[] parameters = method.getParameters();

        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            if(parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class){

                paramMap.put(parameter.getType().getSimpleName() , i);
            }else{
                paramMap.put(parameters[i].getName() , i);
            }
        }
        return paramMap;
    }

    private static void injectBean() throws IllegalAccessException, ClassNotFoundException, InstantiationException {

        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {

            Object value = entry.getValue();

            Field[] fields = value.getClass().getDeclaredFields();

            for (Field field : fields) {
                PrbAutoWired annotation = field.getAnnotation(PrbAutoWired.class);
                if(annotation != null){
                    Object injectBean = getInjectBean(field , annotation);
                    field.setAccessible(true);
                    field.set(value , injectBean);
                }
            }

        }

    }

    private static Object getInjectBean(Field field , PrbAutoWired annotation) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
        Object injectBean;
        if(!annotation.value().equals("")){
            injectBean = getBean(annotation.value());
        }else{
            String name = field.getName();
            injectBean = getBean(name);
        }

        return injectBean;
    }

    private static void doCreateBean() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {

            BeanDefinition beanDefinition = entry.getValue();

            String path = beanDefinition.getPath();
            Object bean = PrbFactoryBean.createBean(path);
            beanMap.put(beanDefinition.getBeanId() , bean);
        }

    }


    public static void buildBeanDefinition(File file) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException {

        if(file.isDirectory()){
            File[] files = file.listFiles();
            for (File file1 : files) {
                buildBeanDefinition(file1);
            }
        }else{
            String path = file.getPath();
            if(path.contains(".class")){
                //解析成class全路径
                String classPath = path.split("classes\\\\")[1].replace("\\", ".").replace(".class", "");
                //获取Class对象
                Class<?> aClass = Class.forName(classPath);
                //判断Class是否有Compoant注解
                PrbService annotation = aClass.getAnnotation(PrbService.class);
                PrbController controller = aClass.getAnnotation(PrbController.class);
                if(annotation != null || controller != null){
                    String beanId = getBeanId(aClass);
                    BeanDefinition beanDefinition = new BeanDefinition();
                    beanDefinition.setBeanId(beanId);
                    beanDefinition.setClassName(aClass.getSimpleName());
                    beanDefinition.setPath(classPath);
                    beanDefinitionMap.put(beanId , beanDefinition);
                }

            }
        }
    }

    public static Object getBean(String beanId) throws IllegalAccessException, InstantiationException, ClassNotFoundException {

        Object bean = beanMap.get(beanId);
        //进行懒加载
        if(bean == null){
            //可能会有安全问题 使用 dcl 防止对象的创建安全
            synchronized (AnnotationBeanFactory.class){
                bean = beanMap.get(beanId);
                if(bean == null){
                    BeanDefinition beanDefinition = beanDefinitionMap.get(beanId);
                    bean = PrbFactoryBean.createBean(beanDefinition.getPath());
                    //如果bean 是单例，则将bean 放入单例池中
                    if(beanDefinition.getIsSingleton() == 1){
                        beanMap.put(beanId , bean);
                    }
                }
            }
        }

        return bean;
    }


    private static String getBeanId(Class<?> clazz){

        Class[] interfaces = clazz.getInterfaces();
        if(interfaces != null && interfaces.length == 1){

            String interfaceName = interfaces[0].getSimpleName();

            return interfaceName.substring(0,1).toLowerCase() + interfaceName.substring(1);

        }
        String simpleName = clazz.getSimpleName();
        return simpleName.substring(0,1).toLowerCase() + simpleName.substring(1);
    }



    private static File getFile(String pagePath) {
        String path = AnnotationBeanFactory.class.getResource("/").getPath();

        String basePage = path + pagePath.replace("." , File.separator);

        return new File(basePage);
    }

}
